package com.cynteka.cyfoman.ui;

import com.cynteka.cyfoman.core.RefItem;
import com.cynteka.cyfoman.core.ValueObject;
import com.cynteka.cyfoman.core.config.AbstractValueObjectListener;
import com.cynteka.cyfoman.core.config.CollectionMD;
import com.cynteka.cyfoman.core.config.ParamMD;
import org.jdesktop.swingx.JXDatePicker;
import org.jdesktop.swingx.autocomplete.AutoCompleteDecorator;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import javax.sql.DataSource;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.*;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.List;

public class BaseParamWidgetFactory implements ParamWidgetFactory {

    private DataSource dataSource;

    private static final RowMapper REF_ITEM_ROW_MAPPER = new RowMapper() {

        public Object mapRow(ResultSet resultSet, int i) throws SQLException {
            return new RefItem(resultSet.getInt("id"), resultSet.getString("name"));
        }
    };


    public BaseParamWidgetFactory() {
    }

    public Component createLabel(ParamMD paramMD) {
        return new JLabel(paramMD.getName());
    }

    @Override
    public Component createRequiredMarker() {
        JLabel res = new JLabel("*");
        res.setForeground(Color.RED);
        return res;
    }

    public Component createEditor(final ParamMD paramMD, final ValueObject value) {
        Component res;
        final Object oValue = value.getParam(paramMD);
        switch (paramMD.getParamType()) {
            case STRING:
                final JTextField tf = new JTextField(oValue == null ? "" : oValue.toString());
                res = tf;

                if (paramMD.getMaxLength() > 0) {
                    tf.setDocument(new JTextFieldLimit(paramMD.getMaxLength()));
                }

                FocusListener listener = new FocusAdapter() {
                    public void focusLost(FocusEvent focusEvent) {
                        value.setParam(paramMD, tf.getText());
                    }
                };

                value.addListener(new AbstractValueObjectListener() {
                    @Override
                    public void prepareToBeStored(ValueObject valueObject) {
                        value.setParam(paramMD, tf.getText());
                    }
                });
                res.addFocusListener(listener);
                decoratePossibleValues(paramMD, tf);
                break;
            case NUMBER:
                final JTextField tfNumber = new JTextField(oValue == null ? "" : oValue.toString());
                res = tfNumber;
                listener = new FocusListener() {
                    public void focusGained(FocusEvent focusEvent) {
                    }

                    public void focusLost(FocusEvent focusEvent) {
                        try {
                            value.setParam(paramMD, new Integer(tfNumber.getText()));
                        } catch (NumberFormatException nfe) {
                            value.setParam(paramMD, tfNumber.getText());
                        }
                    }
                };

                value.addListener(new AbstractValueObjectListener() {
                    @Override
                    public void prepareToBeStored(ValueObject valueObject) {
                        value.setParam(paramMD, new Integer(tfNumber.getText()));
                    }
                });
                res.addFocusListener(listener);
                break;
            case BOOLEAN:
                final JCheckBox cb = new JCheckBox();
                cb.setSelected(Boolean.TRUE.equals(oValue));
                cb.addChangeListener(new ChangeListener() {
                    @Override
                    public void stateChanged(ChangeEvent e) {
                        value.setParam(paramMD, cb.isSelected());
                    }
                });
                value.addListener(new AbstractValueObjectListener() {
                    @Override
                    public void prepareToBeStored(ValueObject valueObject) {
                        value.setParam(paramMD, cb.isSelected());
                    }
                });

                res = cb;
                break;
            case DATE:
                final JXDatePicker datePicker = new JXDatePicker(oValue == null ? null : (Date) oValue);
                res = datePicker;
                listener = new FocusListener() {
                    public void focusGained(FocusEvent focusEvent) {
                    }

                    public void focusLost(FocusEvent focusEvent) {
                        value.setParam(paramMD, datePicker.getDate());
                    }
                };
                datePicker.addFocusListener(listener);
                value.addListener(new AbstractValueObjectListener() {
                    @Override
                    public void prepareToBeStored(ValueObject valueObject) {
                        value.setParam(paramMD, datePicker.getDate());
                    }
                });
                datePicker.getEditor().addFocusListener(listener);
                break;
            case ENUM:
                ButtonGroup bg = new ButtonGroup();
                JPanel panel = new JPanel();
                res = panel;

                int colCount = 1;
                int size = paramMD.getPossibleValues().size();
                int ost = size % colCount;
                int rowCount = (size - ost) / colCount + (ost == 0 ? 0 : 1);

                panel.setLayout(new GridLayout(rowCount, colCount));
                final String[] selected = new String[1];
                for (final String pValue : paramMD.getPossibleValues()) {
                    final JRadioButton radioButton = new JRadioButton(pValue);
                    bg.add(radioButton);
                    panel.add(radioButton);
                    if (pValue.equals(oValue)) {
                        radioButton.setSelected(true);
                    }
                    radioButton.addChangeListener(new ChangeListener() {
                        @Override
                        public void stateChanged(ChangeEvent e) {
                            if (radioButton.isSelected()) {
                                selected[0] = pValue;
                            }
                        }
                    });
                }
                value.addListener(new AbstractValueObjectListener() {
                    @Override
                    public void prepareToBeStored(ValueObject valueObject) {
                        value.setParam(paramMD, selected[0]);
                    }
                });
                break;

            case REFERENCE:
                //*
                final JComboBox combo = new JComboBox();
                AutoCompleteDecorator.decorate(combo);
                res = combo;
                JdbcTemplate select = new JdbcTemplate(dataSource);

                final List<RefItem> items = select.query(paramMD.getPossibleValuesSQL(), REF_ITEM_ROW_MAPPER);

                ComboBoxModel model = new DefaultComboBoxModel() {

                    public int getSize() {
                        return items.size();
                    }

                    public Object getElementAt(int idx) {
                        return items.get(idx);
                    }
                };
                combo.setModel(model);

                for (RefItem item : items) {
                    if (item.id.equals(oValue)) {
                        combo.setSelectedItem(item);
                        break;
                    }
                }

                combo.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        RefItem refItem = (RefItem) combo.getSelectedItem();
                        value.setParam(paramMD, refItem);
                    }
                });

                value.addListener(new AbstractValueObjectListener() {
                    @Override
                    public void prepareToBeStored(ValueObject valueObject) {
                        RefItem refItem = (RefItem) combo.getSelectedItem();
                        value.setParam(paramMD, refItem);
                    }
                });
                return res;
            //*/

            case COLLECTION:
                CollectionMD collectionMD = paramMD.getCollection();
                String sql = collectionMD.getSql();

                JdbcTemplate template = new JdbcTemplate(dataSource);

                List<RefItem> collectionItems = template.query(sql, REF_ITEM_ROW_MAPPER);
                JPanel collectionPanel = new JPanel();
                // JScrollPane scrollPane = new JScrollPane(collectionPanel);
                collectionPanel.setLayout(new GridLayout(collectionItems.size() / 2, 2));

                List<RefItem> refItems = (List<RefItem>) value.getParam(paramMD);
                final Set<RefItem> valueList = new HashSet<RefItem>();
                if (refItems != null)
                    valueList.addAll(refItems);
                for (final RefItem refItem : collectionItems) {
                    final JCheckBox checkBox = new JCheckBox(String.valueOf(refItem.value));
                    checkBox.setSelected(valueList.contains(refItem));
                    collectionPanel.add(checkBox);
                    ChangeListener changeListener = new ChangeListener() {
                        @Override
                        public void stateChanged(ChangeEvent e) {
                            if (checkBox.isSelected()) {
                                valueList.add(refItem);
                            } else {
                                valueList.remove(refItem);
                            }
                            value.setParam(paramMD, valueList);
                        }
                    };
                    checkBox.addChangeListener(changeListener);
                }

                return collectionPanel;
            default:
                throw new IllegalArgumentException("Unknown param type : " + paramMD.getParamType());
        }

        if (res instanceof JComboBox) {
            JComboBox combo = (JComboBox) res;
            combo.setEditable(!paramMD.isReadOnly());
        } else if (res instanceof JTextComponent) {
            JTextComponent textComponent = (JTextComponent) res;
            textComponent.setEditable(!paramMD.isReadOnly());
        } else if (res instanceof JCheckBox) {
            JCheckBox cb = (JCheckBox) res;
            cb.setEnabled(!paramMD.isReadOnly());
        } else if (res instanceof JCheckBox) {
            JCheckBox cb = (JCheckBox) res;
            cb.setEnabled(!paramMD.isReadOnly());
        } else if (res instanceof JXDatePicker) {
            JXDatePicker dp = (JXDatePicker) res;
            dp.setEditable(!paramMD.isReadOnly());
        } else {
            // throw new IllegalStateException("Can not set readonly status for class : " + res.getClass() + " field : " + paramMD.getName());
        }

        return res;
    }

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    protected void decoratePossibleValues(ParamMD paramMD, JTextField tf) {
        List<String> possibleValues = new ArrayList<String>();
        String sql = paramMD.getPossibleValuesSQL();
        if (dataSource != null && sql != null) {
            JdbcTemplate template = new JdbcTemplate(dataSource);

            RowMapper<String> rowMapper = new RowMapper<String>() {
                @Override
                public String mapRow(ResultSet resultSet, int i) throws SQLException {
                    return resultSet.getString(1);
                }
            };
            List<String> possibleValuesFromDB = template.query(sql, rowMapper);
            possibleValues.addAll(possibleValuesFromDB);
        }
        List<String> possibleValuesFromXML = paramMD.getPossibleValues();
        if (possibleValuesFromXML != null) {
            possibleValues.addAll(possibleValuesFromXML);
        }
        if (!possibleValues.isEmpty()) {
            AutoCompleteDecorator.decorate(tf, possibleValues, false);
        }

    }
}
